package cn.com.cetccst.appstore.jssdk;

import static android.util.Log.ERROR;
import static android.util.Log.INFO;

import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;

import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonParseException;
import com.google.gson.annotations.SerializedName;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import cn.com.cetccst.appstore.jssdk.viewmodel.JSSDKViewModel;
import cn.com.cetccst.appstore.log.AppStoreLogger;
import cn.com.cetccst.appstore.network.AppStoreHttpRequester;
import cn.com.cetccst.sdk.modelbase.BaseResp;
import io.reactivex.Observable;
import io.reactivex.disposables.Disposable;
import io.reactivex.schedulers.Schedulers;
import okhttp3.Headers;
import okhttp3.ResponseBody;
import cn.com.cetccst.appstore.tools.Utils;

public class ActionDownloadFile extends BaseJSAction<ActionDownloadFile.RequestPara, String> {
    public static final String ACTION = "downloadFile";

    protected int errorCode = BaseResp.ErrCode.ERROR_FILE_DOWNLOADED_FAILED;
    protected long totalSize;
    protected JSSDKViewModel viewModel;
    protected Disposable disposable;

    ActionDownloadFile(Context context, CXApiImplInnerApp icxapi, String parameters, JSSDKViewModel jssdkViewModel) {
        super(context, icxapi, parameters);
        this.viewModel = jssdkViewModel;
        this.viewModel.isClearDownloadFile = !para.isToExternalStorage;
    }

    @Override
    public void handleAction(Response callback) {
        if (!checkArgs(callback)) {
            return;
        }

        disposable = getDownloadTask()
                .subscribeOn(Schedulers.io())
                .map(call -> startDownloadTask(call))
                .flatMap(response -> handleHttpError(response))
                .doOnNext(response -> handleHeader(response))
                .doOnNext(response -> handleFileName(response))
                .subscribe(response -> {
                    if (!TextUtils.isEmpty(para.name)) {
                        viewModel.addDownloadFile(para.url, para.name);
                        if (writeDataToFile(response.body(), para.name)) {
                            callback.setRespJson(buildSuccessResp(para.name));
                            viewModel.removeDownloadTask(para.url);
                            viewModel.removeDownloadFile(para.url, false);
                            return;
                        }
                    }
                    checkWhetherCacelDownload();
                    callback.setRespJson(buildFailedResp(errorCode));
                    if (!disposable.isDisposed())
                        disposable.dispose();
                }, error -> {
                    AppStoreLogger.info("download file url : " + para.url + " ,error " + error);
                    callback.setRespJson(buildFailedResp(errorCode));
                });

    }

    protected Observable<okhttp3.Call> getDownloadTask() {
        return Observable.create(emitter -> {
            okhttp3.Request request = new okhttp3.Request.Builder()
                    .headers(getHeaders())
                    .url(para.url)
                    .build();
            emitter.onNext(AppStoreHttpRequester.getInstance().getOKHttpClient().newCall(request));
        });
    }

    private okhttp3.Response startDownloadTask(okhttp3.Call call) throws IOException {
        viewModel.addDownloadTask(para.url, call);
        return call.execute();
    }

    private Observable<okhttp3.Response> handleHttpError(okhttp3.Response response) {
        if (response.isSuccessful()) {
            return Observable.just(response);
        } else {
            errorCode = response.code();
            return Observable.error(new Exception("http download failed"));
        }
    }

    private void handleHeader(okhttp3.Response response) {
        if (TextUtils.isEmpty(para.name)) {
            para.name = parseContentDisposition(response.headers().get("Content-disposition"));
            AppStoreLogger.info( "parseContentDisposition filename: " + para.name);
            para.name = Uri.decode(para.name);
            AppStoreLogger.info( "parseContentDisposition decode filename: " + para.name);
        }
        if (TextUtils.isEmpty(para.name)) {
            para.name = parseFileNameFromUrl(para.url);
            AppStoreLogger.info( "parseFileNameFromUrl filename: " + para.name);
        }
        String tempTotalSize = response.headers().get("Content-Length");
        if (!TextUtils.isEmpty(tempTotalSize)) {
            totalSize = Long.valueOf(tempTotalSize);
        }
    }

    private void handleFileName(okhttp3.Response response) {
        para.name = checkFileName(para.name);
    }

    private void checkWhetherCacelDownload() {
        //当取消下载时，会先删除下载任务，再中止下载进程，所以可以用以下判断来确定是取消下载任务
        if (viewModel.downloadTask.get(para.url) == null) {
            errorCode = BaseResp.ErrCode.ERROR_FILE_CANCEL_DOWNLOAD;
        }
    }

    private String parseContentDisposition(String contentDisposition) {
        /** Regex used to parse content-disposition headers */
        //示例 content = "attachment; filename=\"%E6%97%A5%E6%8A%A5.xlsx\""
        final Pattern CONTENT_DISPOSITION_PATTERN = Pattern.compile("attachment;\\s*filename\\s*=\\s*\"([^\"]*)\"");
        try {
            Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition);
            if (m.find()) {
                return m.group(1);
            }
        } catch (Exception ex) {
            AppStoreLogger.info( TAG, "parseContentDisposition error: ", ex);
        }
        return null;
    }

    private String parseFileNameFromUrl(String url) {
        String decodedUrl = Uri.decode(url);
        if (decodedUrl != null) {
            int queryIndex = decodedUrl.indexOf('?');
            // If there is a query string strip it, same as desktop browsers
            if (queryIndex > 0) {
                decodedUrl = decodedUrl.substring(0, queryIndex);
            }
            if (!decodedUrl.endsWith("/")) {
                int index = decodedUrl.lastIndexOf('/') + 1;
                if (index > 0) {
                    return decodedUrl.substring(index);
                }
            }
        }
        return null;
    }

    //本地存储目录
    private String checkFileName(String fileName) {
        if (TextUtils.isEmpty(fileName)) {
            return fileName;
        }
        String path = getFilePath();
        String name = fileName;
        String extension = "";
        if (fileName.lastIndexOf(".") > 0) {
            name = fileName.substring(0, fileName.lastIndexOf("."));
            extension = fileName.substring(fileName.lastIndexOf("."));
        }
        return chooseUniqueFilename(path, name, extension);
    }

    private boolean writeDataToFile(ResponseBody data, String fullPathName) {
        AppStoreLogger.info( TAG, "writeDataToFile");
        try {
            BufferedInputStream bis = new BufferedInputStream(data.byteStream());
            File file = new File(fullPathName);
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
            if (file.exists()) {
                file.delete();
            }
            FileOutputStream fos = new FileOutputStream(file, true);
            try {

                byte[] buffer = new byte[1024];
                int len;
                int total_length = 0;
                while ((len = bis.read(buffer)) != -1) {
                    fos.write(buffer, 0, len);
                    fos.flush();
                    total_length += len;
                    notifyProcess(total_length);
                }
                AppStoreLogger.info( TAG, "writeDataToFile total length: " + total_length);
                //通知系统,创建了文件,如果不发送此通知,当从文件管理器删除此文件后,再次存储时将报文件已存在错误
                context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file)));
                return true;
            } catch (Exception e) {
                AppStoreLogger.info(TAG, "writeDataToFile Exception:", e);
            } finally {
                bis.close();
                fos.close();
            }

        } catch (Exception e) {
            AppStoreLogger.info( TAG, "writeDataToFile close stream error: ", e);
        }
        return false;
    }

    private String chooseUniqueFilename(String path, String filename, String extension) {
        String fullFilename = path + File.separator + filename + extension;
        if (!new File(fullFilename).exists()) {
            return fullFilename;
        }


        for (int index = 1; index < 10000; index++) {
            fullFilename = path + File.separator + filename + "(" + index + ")" + extension;
            Log.v("fileName", fullFilename);
            if (!new File(fullFilename).exists()) {
                return fullFilename;
            }
        }

        return fullFilename;
    }

    protected Headers getHeaders() {
        Headers.Builder headersBuild = new Headers.Builder();
        if (para.headers != null) {
            for (String key : para.headers.keySet()) {
                headersBuild.add(key, para.headers.get(key));
            }
        }
        return headersBuild.build();
    }


    protected boolean checkArgs(Response callback) {
        if (!checkBaseArgs(callback)) {
            return false;
        }
        if (TextUtils.isEmpty(para.url)) {
            AppStoreLogger.info( TAG, "parameter error!");
            callback.setRespJson(buildFailedResp(BaseResp.ErrCode.ERR_PARAMETER_NULL));
            return false;
        }

        try {
            getHeaders();
        } catch (Exception e) {
            AppStoreLogger.info( TAG, "header is Illegal: ", e);
            callback.setRespJson(buildFailedResp(BaseResp.ErrCode.ERR_PARAMETER_NULL));
            return false;
        }

        //CXV3-9730 撤销客户端存储权限，导出信息报送统计数据，提示“下载文件失败”
        if (context instanceof Activity && ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
            callback.setRespJson(buildFailedResp(BaseResp.ErrCode.ERROR_PERMISSION_WRITE_SDCARD));
            new Thread(() -> {
                try {
                    //授权弹窗会把吐司立即关闭，所以延迟一下
                    Thread.sleep(1500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ActivityCompat.requestPermissions((Activity) context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0x1000);
            }).start();

            return false;
        }
        return true;
    }

    @Override
    protected RequestPara parserParameters(String para) {
        Gson gson = new GsonBuilder()
                .registerTypeAdapter(Map.class, new StringToMapTypeAdapter())
                .create();
        return gson.fromJson(para, RequestPara.class);
    }

    private static class StringToMapTypeAdapter implements JsonDeserializer<Map> {
        public Map deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
            return Utils.getGson().fromJson(json, Map.class);
        }
    }

    @Override
    protected String buildSuccessResp(String fileName) {
        Resp<RespData> resp = new Resp();
        resp.code = 0;
        resp.data = new RespData();
        resp.data.filePath = fileName;
        resp.data.name = new File(fileName).getName();
        return Utils.getGson().toJson(resp);
    }

    static class RequestPara {
        @SerializedName("url")
        String url;
        @SerializedName("name")
        String name;
        @SerializedName("headers")
        Map<String, String> headers;
        @SerializedName("isToExternalStorage")
        boolean isToExternalStorage = true;
    }

    static class RespData {
        @SerializedName("filePath")
        String filePath;
        @SerializedName("name")
        String name;
    }

    protected void notifyProcess(int currentBytes) {
    }

    public String getFilePath() {
        if (para.isToExternalStorage) {
            return getExternalDownloadDir(context);
        } else {
//            return getDownloadDir(context, icxapi.getAppKey());
            return getDownloadDir(context, "emm");
        }
    }

    public static String getDownloadDir(Context context, String subPath) {
        return context.getCacheDir().getAbsolutePath()
                + File.separator + "workbench" + File.separator + subPath;
    }

    public static String getExternalDownloadDir(Context context) {
        return Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_DOWNLOADS).getAbsolutePath() + File.separator + "westone";
    }
}
